﻿#pragma once

#include <chrono>
#include <cstdint>
#include <ctime>
#include <initializer_list>
#include <stdexcept>
#include <string>

namespace kratos {
namespace service {
class ServiceBox;
} // service
} // namespace kratos

namespace kratos {
namespace time_system {

// Enumerations for week day
enum class Weekday : uint8_t {
  Sunday = 0, // SUNDAY
  Monday,     // MONDAY
  Tuesday,    // TUESDAY
  Wednesday,  // WEDNESDAY
  Thursday,   // THURSDAY
  Friday,     // FRIDAY
  Saturday,   // SATURDAY
};

// Day time info.
struct Datetime {
  int year{0};                   // Year
  int month{0};                  // Month
  int day{0};                    // Day
  Weekday wday{Weekday::Sunday}; // week day @see WeekDay
  int hour{0};                   // Hour
  int minute{0};                 // Minute
  int second{0};                 // Second
  // ctor
  Datetime() noexcept;
  // ctor
  // @param t timestamp in second
  Datetime(std::time_t t) noexcept;
  // ctor
  // @param fmt like 1970/01/01 00:00:00
  Datetime(const std::string &fmt) noexcept(false);
  // ctor
  // @param params the integer sequence like: year, month, day, hour, minute,
  // second
  Datetime(const std::initializer_list<int> &params) noexcept;
  // returns timestamp in second(CIVIL)
  std::time_t get_second();
  // returns timestamp in second(UTC)
  std::time_t get_second_utc();
  // returns human readable string, like 1970/01/01 00:00:00
  std::string to_string();
};

// Time utilities
struct TimeSystem {
  ///////////////////////////////////////////////////////////////////////////
  //// copy from http://howardhinnant.github.io/date_algorithms.html
  ///////////////////////////////////////////////////////////////////////////

  // Returns number of days since civil 1970-01-01.  Negative values indicate
  //    days prior to 1970-01-01.
  // Preconditions:  y-m-d represents a date in the civil (Gregorian) calendar
  //                 m is in [1, 12]
  //                 d is in [1, last_day_of_month(y, m)]
  //                 y is "approximately" in
  //                   [numeric_limits<Int>::min()/366,
  //                   numeric_limits<Int>::max()/366]
  //                 Exact range of validity is:
  //                 [civil_from_days(numeric_limits<Int>::min()),
  //                  civil_from_days(numeric_limits<Int>::max()-719468)]
  template <class Int>
  static Int days_from_civil(Int y, unsigned m, unsigned d) noexcept {
    static_assert(
        std::numeric_limits<unsigned>::digits >= 18,
        "This algorithm has not been ported to a 16 bit unsigned integer");
    static_assert(
        std::numeric_limits<Int>::digits >= 20,
        "This algorithm has not been ported to a 16 bit signed integer");
    y -= m <= 2;
    const Int era = (y >= 0 ? y : y - 399) / 400;
    const unsigned yoe = static_cast<unsigned>(y - era * 400); // [0, 399]
    const unsigned doy =
        (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;         // [0, 365]
    const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096]
    return era * 146097 + static_cast<Int>(doe) - 719468;
  }

  // Returns year/month/day triple in civil calendar
  // Preconditions:  z is number of days since 1970-01-01 and is in the range:
  //                   [numeric_limits<Int>::min(),
  //                   numeric_limits<Int>::max()-719468].
  template <class Int>
  static std::tuple<Int, unsigned, unsigned> civil_from_days(Int z) noexcept {
    static_assert(
        std::numeric_limits<unsigned>::digits >= 18,
        "This algorithm has not been ported to a 16 bit unsigned integer");
    static_assert(
        std::numeric_limits<Int>::digits >= 20,
        "This algorithm has not been ported to a 16 bit signed integer");
    z += 719468;
    const Int era = (z >= 0 ? z : z - 146096) / 146097;
    const unsigned doe = static_cast<unsigned>(z - era * 146097); // [0, 146096]
    const unsigned yoe =
        (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399]
    const Int y = static_cast<Int>(yoe) + era * 400;
    const unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365]
    const unsigned mp = (5 * doy + 2) / 153;                      // [0, 11]
    const unsigned d = doy - (153 * mp + 2) / 5 + 1;              // [1, 31]
    const unsigned m = mp + (mp < 10 ? 3 : -9);                   // [1, 12]
    return std::tuple<Int, unsigned, unsigned>(y + (m <= 2), m, d);
  }

  // Returns: true if y is a leap year in the civil calendar, else false
  template <class Int> static bool is_leap(Int y) noexcept {
    return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
  }

  // Preconditions: m is in [1, 12]
  // Returns: The number of days in the month m of common year
  // The result is always in the range [28, 31].
  static unsigned last_day_of_month_common_year(unsigned m) noexcept {
    constexpr static unsigned char a[] = {31, 28, 31, 30, 31, 30,
                                          31, 31, 30, 31, 30, 31};
    return a[m - 1];
  }

  // Preconditions: m is in [1, 12]
  // Returns: The number of days in the month m of leap year
  // The result is always in the range [29, 31].
  static unsigned last_day_of_month_leap_year(unsigned m) noexcept {
    constexpr static unsigned char a[] = {31, 29, 31, 30, 31, 30,
                                          31, 31, 30, 31, 30, 31};
    return a[m - 1];
  }

  // Preconditions: m is in [1, 12]
  // Returns: The number of days in the month m of year y
  // The result is always in the range [28, 31].
  template <class Int>
  static unsigned last_day_of_month(Int y, unsigned m) noexcept {
    return m != 2 || !is_leap(y) ? last_day_of_month_common_year(m) : 29u;
  }

  // Returns day of week in civil calendar [0, 6] -> [Sun, Sat]
  // Preconditions:  z is number of days since 1970-01-01 and is in the range:
  //                   [numeric_limits<Int>::min(),
  //                   numeric_limits<Int>::max()-4].
  template <class Int> static unsigned weekday_from_days(Int z) noexcept {
    return static_cast<unsigned>(z >= -4 ? (z + 4) % 7 : (z + 5) % 7 + 6);
  }

  // Preconditions: x <= 6 && y <= 6
  // Returns: The number of days from the weekday y to the weekday x.
  // The result is always in the range [0, 6].
  static unsigned weekday_difference(unsigned x, unsigned y) noexcept {
    x -= y;
    return x <= 6 ? x : x + 7;
  }

  // Preconditions: wd <= 6
  // Returns: The weekday following wd
  // The result is always in the range [0, 6].
  static unsigned next_weekday(unsigned wd) noexcept {
    return wd < 6 ? wd + 1 : 0;
  }

  // Preconditions: wd <= 6
  // Returns: The weekday prior to wd
  // The result is always in the range [0, 6].
  static unsigned prev_weekday(unsigned wd) noexcept {
    return wd > 0 ? wd - 1 : 6;
  }

  // main cycle of time library
  static void update();
  // main cycle of time library
  // @param current current timestamp in millionseconds
  static void update(std::time_t current);
  // try to open time system
  // @return true或false
  static bool open_time_system(kratos::service::ServiceBox* box);
  // get open flag
  static bool is_open_time_system();
  // get timestamp in nanoseconds from OS
  static std::time_t get_nanosecond_os();
  // get timestamp in millionseconds from time utility library
  static std::time_t get_millionsecond();
  // get timestamp in millionseconds from OS
  static std::time_t get_millionsecond_os();
  // get timestamp in seconds from time utility library
  static std::time_t get_second();
  // get timestamp in seconds from OS
  static std::time_t get_second_os();
  // returns difference between UTC and CIVIL in seconds
  static std::time_t get_utc_diff_second();
  struct CIVIL {
    // get struct tm* from now(CIVIL)
    static const struct tm *get_tm();
    // returns number of days from now to 't'(CIVIL)
    static int get_days(std::time_t t);
    // returns number of days from 't1' to 't2'(CIVIL)
    static int get_days(std::time_t t1, std::time_t t2);
    // get DayTime from now(CIVIL)
    static const Datetime &get_datetime();
    // returns Datetime by 't'(CIVIL)
    static const Datetime get_datetime(std::time_t t);
    // returns timestamp in seconds from date time(CIVIL)
    static std::time_t get_second_from_datetime(const Datetime &datetime);
    // Is in the same day?(CIVIL)
    static bool in_same_day(std::time_t t1, std::time_t t2);
    // Is in the same day?(CIVIL), compare to now
    static bool in_same_day(std::time_t t);
    // Is in the same week?(CIVIL)
    static bool in_same_week(std::time_t t1, std::time_t t2);
    // Is in the same week?(CIVIL), compare to now
    static bool in_same_week(std::time_t t);
    // Is in the same month?(CIVIL)
    static bool in_same_month(std::time_t t1, std::time_t t2);
    // Is in the same month?(CIVIL), compare to now
    static bool in_same_month(std::time_t t);
  };
  struct UTC {
    // get struct tm* from now(UTC)
    static const struct tm *get_tm_utc();
    // get DayTime from time now(UTC)
    static const Datetime &get_datetime_utc();
    // returns Datetime by 't'(UTC)
    static const Datetime get_datetime_utc(std::time_t t);
    // returns number of days from now to 't'(UTC)
    static int get_days_utc(std::time_t t);
    // returns number of days from 't1' to 't2'(UTC)
    static int get_days_utc(std::time_t t1, std::time_t t2);
    // returns timestamp in seconds from date time(UTC)
    static std::time_t get_second_from_datetime_utc(const Datetime &datetime);
    // Is in the same day?(UTC)
    static bool in_same_day_utc(std::time_t t1, std::time_t t2);
    // Is in the same day?(UTC), compare to now
    static bool in_same_day_utc(std::time_t t);
    // Is in the same week?(UTC)
    static bool in_same_week_utc(std::time_t t1, std::time_t t2);
    // Is in the same week?(UTC), compare to now
    static bool in_same_week_utc(std::time_t t);
    // Is in the same month?(UTC)
    static bool in_same_month_utc(std::time_t t1, std::time_t t2);
    // Is in the same month?(UTC), compare to now
    static bool in_same_month_utc(std::time_t t);
  };
};

} // namespace time_system
} // namespace kratos
